#include "proxy/channel/client_conn_pair.h"

namespace proxy{

  int ClientConnPair::id_ = 0;

  ClientConnPair::ClientConnPair(socket_ptr connect, FlowInstance::Ptr flow_instance)
    :client_socket_(connect),
    io_service_(connect->get_io_service()),
    flow_instance_(flow_instance){
    LOG(WARNING) << "Create ClientConnPair " << id_++;
  }

  ClientConnPair::~ClientConnPair(){
    LOG(WARNING) << "Delete ~ClientConnPair " << --id_;
  }

  bool ClientConnPair::Start(const std::string addr, uint16 port){

    encry_conn_.reset(new proxy::EncryClientConn(io_service_));
    encry_conn_->SetConnErrorCallback(
      boost::bind(&ClientConnPair::OnConnError, shared_from_this(), _1, _2, _3));
    encry_conn_->SetConnReadCallback(
      boost::bind(&ClientConnPair::OnConnRead, shared_from_this(), _1, _2, _3));
    encry_conn_->SetConnWriteCallback(
      boost::bind(&ClientConnPair::OnConnWrite, shared_from_this(), _1));
    encry_conn_->SetConnServerCallback(
      boost::bind(&ClientConnPair::OnConnServer, shared_from_this(), _1, _2));
    encry_conn_->Start(addr, port);
    return true;
  }

  void ClientConnPair::OnConnRead(proxy::EncryClientConn::Ptr encry_conn,
    const char* data, int size){
    flow_instance_->AddDownFlow(size);
    //DLOG(INFO).write(data, size);
    boost::asio::async_write(*client_socket_,
      boost::asio::buffer(data, size),
      boost::bind(&ClientConnPair::HandleClientWrite, shared_from_this(),
      encry_conn, boost::asio::placeholders::error));
  }

  void ClientConnPair::OnConnWrite(proxy::EncryClientConn::Ptr encry_conn){
    DLOG(INFO) << "Write complete";
    ReadClientData();
    //encry_conn->AsyncWriteData(test_data, strlen(test_data));
  }

  void ClientConnPair::OnConnError(proxy::EncryClientConn::Ptr encry_conn,
    const boost::system::error_code& err, ErrorType error_type){
    DLOG(ERROR) << err.message();
    //if (client_socket_->is_open()){
    //  client_socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
    //  // client_socket_->close();
    //}
    if (error_type == ERROR_TYPE_WRITE_ERROR){
      client_socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_receive);
    }
    else if (error_type == ERROR_TYPE_READ_ERROR){
      client_socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_receive);

    }
    if (encry_conn_){
      encry_conn_.reset();
    }
  }

  void ClientConnPair::OnConnServer(proxy::EncryClientConn::Ptr encry_conn,
    const boost::system::error_code& err){
    if (err){
      DLOG(ERROR) << "Connect server error";
      Respone503Error();
      return;
    }
    DLOG(INFO) << "Connect server succeed";
    ReadClientData();
  }

  void ClientConnPair::Respone503Error(){
    reply_ = Reply::stock_reply(Reply::service_unavailable);
    boost::asio::async_write(*client_socket_, reply_.to_buffers(),
      boost::bind(&ClientConnPair::HandleClientWriteClose, shared_from_this(),
      boost::asio::placeholders::error, 
      boost::asio::placeholders::bytes_transferred));
  }

  void ClientConnPair::HandleClientWriteClose(
    boost::system::error_code err, int size){
    flow_instance_->AddDownFlow(size);
    if (client_socket_->is_open()){
      client_socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_both, err);
    }
  }

  void ClientConnPair::ReadClientData(){

    client_socket_->async_read_some(boost::asio::buffer(buffer_, MAX_BUFFER_SIZE),
      boost::bind(&ClientConnPair::HandleClientRead, shared_from_this(),
      encry_conn_, boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred));
  }

  void ClientConnPair::HandleClientRead(proxy::EncryClientConn::Ptr encry_conn,
    const boost::system::error_code& err, int size){
    if (err){
      DLOG(ERROR) << "Write data to server error " << err.message();
      if (encry_conn_){
        encry_conn_->Stop(); 
      }
      return;
    }
    flow_instance_->AddUpFlow(size);
    //DLOG(INFO).write(buffer_, size);
    encry_conn->AsyncWriteData(buffer_, size);
  }

  void ClientConnPair::HandleClientWrite(proxy::EncryClientConn::Ptr encry_conn,
    boost::system::error_code err){
    if (err){
      DLOG(ERROR) << "Write data to client error " << err.message();
      if (encry_conn_){
        encry_conn_->Stop();
      }
      return;
    }
    encry_conn->StartReadEncryData();
  }
}